package config

import (
	"crypto/rand"
	"encoding/hex"
	"errors"
	"flag"
	"fmt"
	"io/fs"
	"os"
	"path/filepath"
	"time"

	"gopkg.in/yaml.v3"
)

// Q 是一个总是以双引号序列化的字符串类型
type Q string

func (s Q) MarshalYAML() (interface{}, error) {
	n := yaml.Node{Kind: yaml.ScalarNode, Tag: "!!str", Value: string(s), Style: yaml.DoubleQuotedStyle}
	return &n, nil
}

func (s *Q) UnmarshalYAML(value *yaml.Node) error {
	if value.Kind != yaml.ScalarNode {
		return fmt.Errorf("expect scalar for string, got kind=%d", value.Kind)
	}
	*s = Q(value.Value)
	return nil
}

type Config struct {
	Version int `yaml:"version"`
	Server  struct {
		Listen   Q   `yaml:"listen"`
		Port     int `yaml:"port"`
		DataRoot Q   `yaml:"dataRoot"`
	} `yaml:"server"`
	Auth struct {
		SuperAdmin struct {
			Username     Q `yaml:"username"`
			PasswordHash Q `yaml:"passwordHash"`
		} `yaml:"superAdmin"`
	} `yaml:"auth"`
	S3 struct {
		Region          Q `yaml:"region"`
		AddressingStyle Q `yaml:"addressingStyle"`
	} `yaml:"s3"`
	Storage struct {
		VolumeSize int64 `yaml:"volumeSize"` // 卷大小，单位字节，默认200MB
	} `yaml:"storage"`
	Logging struct {
		Level  Q `yaml:"level"`
		Format Q `yaml:"format"`
		Dir    Q `yaml:"dir"`
	} `yaml:"logging"`
}

func defaultConfig() Config {
	var c Config
	c.Version = 1
	c.Server.Listen = Q("0.0.0.0")
	c.Server.Port = 8000
	c.Server.DataRoot = Q("runtime/s3-data")
	c.Auth.SuperAdmin.Username = Q("admin")
	// 留空哈希，首次生成时写入随机口令并打印一次
	c.S3.Region = Q("us-east-1")
	c.S3.AddressingStyle = Q("path")
	c.Storage.VolumeSize = 200 * 1024 * 1024 // 200MB
	c.Logging.Level = Q("info")
	c.Logging.Format = Q("console")
	c.Logging.Dir = Q("runtime/logs")
	return c
}

func genRandomPassword() (string, error) {
	b := make([]byte, 12)
	if _, err := rand.Read(b); err != nil {
		return "", err
	}
	return hex.EncodeToString(b), nil
}

type Options struct {
	ConfigPath string
	InitOnly   bool
	PrintOnly  bool
}

func ParseFlags() Options {
	var opt Options
	flag.StringVar(&opt.ConfigPath, "config", "", "path to server.yaml")
	flag.BoolVar(&opt.InitOnly, "init", false, "generate default server.yaml and exit")
	flag.BoolVar(&opt.PrintOnly, "print-config", false, "print effective config and exit")
	flag.Parse()
	return opt
}

func resolveConfigPath(opt Options) (string, bool) {
	// 1. --config
	if opt.ConfigPath != "" {
		return opt.ConfigPath, true
	}
	// 2. env
	if env := os.Getenv("JDFS2_CONFIG"); env != "" {
		return env, true
	}
	// 3. runtime directory
	exe, _ := os.Executable()
	if exe != "" {
		p := filepath.Join(filepath.Dir(exe), "runtime", "server.yaml")
		return p, false
	}
	return "runtime/server.yaml", false
}

func ensureDir(path string) error {
	if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
		return err
	}
	return nil
}

func backupExistingConfig(path string) error {
	if _, err := os.Stat(path); os.IsNotExist(err) {
		return nil // 文件不存在，无需备份
	}

	// 生成备份文件名：server.yaml.2024-09-18_23-45-30
	timestamp := time.Now().Format("2006-01-02_15-04-05")
	backupPath := fmt.Sprintf("%s.%s", path, timestamp)

	// 复制文件
	if err := copyFile(path, backupPath); err != nil {
		return fmt.Errorf("failed to backup config file: %w", err)
	}

	fmt.Printf("Existing config backed up to: %s\n", backupPath)
	return nil
}

func copyFile(src, dst string) error {
	sourceFile, err := os.Open(src)
	if err != nil {
		return err
	}
	defer sourceFile.Close()

	destFile, err := os.Create(dst)
	if err != nil {
		return err
	}
	defer destFile.Close()

	_, err = destFile.ReadFrom(sourceFile)
	return err
}

// ResolvePath 解析相对路径为绝对路径
// 如果路径已经是绝对路径，直接返回
// 如果是相对路径，则基于可执行文件所在目录进行解析
func ResolvePath(relativePath string) string {
	if filepath.IsAbs(relativePath) {
		return relativePath
	}

	// 获取可执行文件所在目录
	exe, err := os.Executable()
	if err != nil {
		// 如果无法获取可执行文件路径，使用当前工作目录
		wd, _ := os.Getwd()
		return filepath.Join(wd, relativePath)
	}

	exeDir := filepath.Dir(exe)
	return filepath.Join(exeDir, relativePath)
}

// GetDataRoot 获取数据根目录的绝对路径
func (c *Config) GetDataRoot() string {
	return ResolvePath(string(c.Server.DataRoot))
}

// GetLogDir 获取日志目录的绝对路径
func (c *Config) GetLogDir() string {
	return ResolvePath(string(c.Logging.Dir))
}

// GetPIDFile 获取PID文件的绝对路径
func (c *Config) GetPIDFile() string {
	return filepath.Join(ResolvePath("runtime"), "app.pid")
}

// GetLogFile 获取日志文件的绝对路径
func (c *Config) GetLogFile() string {
	return filepath.Join(c.GetLogDir(), "server.log")
}

func LoadOrCreate(opt Options) (Config, string, error) {
	return LoadOrCreateWithBackup(opt, false)
}

func LoadOrCreateWithBackup(opt Options, backup bool) (Config, string, error) {
	path, explicit := resolveConfigPath(opt)
	var cfg Config

	// 如果需要备份且文件存在，先备份
	if backup {
		if err := backupExistingConfig(path); err != nil {
			return Config{}, path, err
		}
	}

	// try read (only if not forcing backup)
	if !backup {
		data, err := os.ReadFile(path)
		if err == nil {
			if yamlErr := yaml.Unmarshal(data, &cfg); yamlErr != nil {
				return Config{}, path, yamlErr
			}
			return cfg, path, nil
		}
		if !errors.Is(err, fs.ErrNotExist) {
			// cannot read for other reasons; if explicit path given, fail
			if explicit {
				return Config{}, path, err
			}
		}
	}

	// create default
	cfg = defaultConfig()
	if pwd, e := genRandomPassword(); e == nil {
		// 明文仅打印一次，不落盘哈希实现简单起见先写明文（教学用途）
		cfg.Auth.SuperAdmin.PasswordHash = Q(pwd)
		if backup {
			fmt.Printf("Super admin username: admin\n")
			fmt.Printf("Super admin password: %s\n", pwd)
		} else {
			fmt.Printf("[init] super admin username=admin password=%s\n", pwd)
		}
	}

	if err := ensureDir(path); err != nil {
		return Config{}, path, err
	}
	out := generateConfigWithComments(cfg)
	if writeErr := os.WriteFile(path, out, 0600); writeErr != nil {
		return Config{}, path, writeErr
	}
	return cfg, path, nil
}

// generateConfigWithComments 生成带注释的配置文件内容
func generateConfigWithComments(cfg Config) []byte {
	// 首先生成基本的YAML内容
	basicYAML, _ := yaml.Marshal(cfg)

	// 添加注释说明
	comment := `# JDFS2 配置文件
# 路径说明：
# - 相对路径：相对于可执行文件(jdfs2)所在目录
# - 绝对路径：直接使用，如 /var/lib/jdfs2/data
# 示例：
#   dataRoot: "runtime/s3-data"        # 相对路径 -> /path/to/jdfs2/runtime/s3-data
#   dataRoot: "/var/lib/jdfs2/data"    # 绝对路径 -> /var/lib/jdfs2/data

`

	return append([]byte(comment), basicYAML...)
}
